package com.colin.util.util;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.net.URLDecoder;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @des: RSA非对称加密解密
 */
public class RsaUtils {
    /**
     * 日志实例
     */
    private static final Logger logger = LoggerFactory.getLogger(RsaUtils.class);
    /**
     * 算法类型
     */
    public static String RSA_ALGORITHM = "RSA";
    /**
     * 字符编码
     */
    public static String UTF8 = "UTF-8";
    /**
     * 密钥长度，DSA算法的默认密钥长度是1024
     * 密钥长度必须是64的倍数，在512到65536位之间
     */
    private static final int KEY_SIZE = 1024;
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 测试
     */
    public static void main(String[] args) {
        //生成随机密码和加解密测试
        //testEncryptDecrypt();

        //资产标签加解密测试。
        assetLabelEncryptAndDecrytTest();
    }

    /**
     * 资产标签加解密测试。
     * （1）分解解密可以处理直接加密的数据，直接解密可以处理。
     */
    public static void assetLabelEncryptAndDecrytTest(){
        //资产标签链接RSA加密公钥
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3XeCuet7W8RELPhASTDe7I2ObXmtdySsaY9E3KtXVkafJ1Mzm/KySwdPTdRt3GZL5vGWyNJBV+RZjy1usaOjJMBUNLNKVCTii/eVVrWkGVmUwd7rlJdQfoCz2WHv+nqN0rlaYUeX6vivzM8lro0rglJAlgUe6EVt9gBeCuOntUQIDAQAB";
        String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALdd4K563tbxEQs+EBJMN7sjY5tea13JKxpj0Tcq1dWRp8nUzOb8rJLB09N1G3cZkvm8ZbI0kFX5FmPLW6xo6MkwFQ0s0pUJOKL95VWtaQZWZTB3uuUl1B+gLPZYe/6eo3SuVphR5fq+K/MzyWujSuCUkCWBR7oRW32AF4K46e1RAgMBAAECgYBZqAEvzswedIhM4u+gwruNOOR/F0+P78ivgpbNTph3cFyGBxKY7nrC1xrnjZ78QPjoOwHOfH0GTGByMwCD8FfBE1UDB728GSLLvO8oW37dWRmw9qNhE0aaXO2sVsU4Zz6LJNRvvll4TLB2pHibGUmw1uZ6/LdJxqBCvZqEe87JMQJBAN9PuOmptmcV/jMYlNM3FilbQoE2JFcJ0FGfdMQUgujN5h8z3ZPYXw6l6bJp79YEYxF9MGjeee7EnooYvvmIU+cCQQDSNUjIutiidraP2YaC5bdbu/jjqWi3NZK85ZEjheFLfiuAFnQpQYwrcW5JjT+EgU3suempWHxTNc85QKxefw4HAkAQwDYqsORRz0CGLK3dOiCbcZEHiDwpmz7ZIJDttGwtcmR0qrtQP8bBnohtLa0j7KhRw4ZCF44l7vHFgh2xMhy5AkAY83AvO1ZpelPTIUvBD8lC3z38r+gZDwfa2hjvMX37ev9o1cF2ACzGH1Gq81Qdw0dfmPWuPy2KpvFw50dOdCBTAkEA0XPMVJm+pQm5p3Vkov54COcoOLprKjTLinJg196hZdp66AVpr+S4dKssbSFSlm4XVrx7Kul4mPO8+/PcNhMAkg==";
        //String text = "{\"assetCode\":\"0100001201\",\"rootCompanyId\":121}";
        String text = "{\"assetCode\":\"一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十123450\",\"rootCompanyId\":121}";
        String s1 = encrypt(text, publicKey);
        System.out.println("S1直接加密：" + s1);
        String s2 = blockEncryptByPublicKey(text, publicKey); //分段加密
        System.out.println("S2分段加密：" + s2);
        String decrypt1 = decrypt(s1, privateKey);
        System.out.println("S1直接解密：" + decrypt1);
        String decrypt2 = decrypt(s2, privateKey);
        System.out.println("S2直接解密：" + decrypt2);
        String decrypt3 = blockDecryptByPrivateKey(s1, privateKey);
        System.out.println("S1分段解密：" + decrypt3);
        String decrypt4 = blockDecryptByPrivateKey(s2, privateKey);
        System.out.println("S2分段解密：" + decrypt4);

        decrypt4 = URLDecoder.decode(decrypt4); //URL解密
        System.out.println("S2分段解密URL解密：" + decrypt4);
    }

    /**
     * 解密资产标签前端得到的加密数据
     *
     * @param content 密文内容
     */
    public static String assetLableWebDecrytDemo(String content) {
        //固定解密私钥
        String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALdd4K563tbxEQs+EBJMN7sjY5tea13JKxpj0Tcq1dWRp8nUzOb8rJLB09N1G3cZkvm8ZbI0kFX5FmPLW6xo6MkwFQ0s0pUJOKL95VWtaQZWZTB3uuUl1B+gLPZYe/6eo3SuVphR5fq+K/MzyWujSuCUkCWBR7oRW32AF4K46e1RAgMBAAECgYBZqAEvzswedIhM4u+gwruNOOR/F0+P78ivgpbNTph3cFyGBxKY7nrC1xrnjZ78QPjoOwHOfH0GTGByMwCD8FfBE1UDB728GSLLvO8oW37dWRmw9qNhE0aaXO2sVsU4Zz6LJNRvvll4TLB2pHibGUmw1uZ6/LdJxqBCvZqEe87JMQJBAN9PuOmptmcV/jMYlNM3FilbQoE2JFcJ0FGfdMQUgujN5h8z3ZPYXw6l6bJp79YEYxF9MGjeee7EnooYvvmIU+cCQQDSNUjIutiidraP2YaC5bdbu/jjqWi3NZK85ZEjheFLfiuAFnQpQYwrcW5JjT+EgU3suempWHxTNc85QKxefw4HAkAQwDYqsORRz0CGLK3dOiCbcZEHiDwpmz7ZIJDttGwtcmR0qrtQP8bBnohtLa0j7KhRw4ZCF44l7vHFgh2xMhy5AkAY83AvO1ZpelPTIUvBD8lC3z38r+gZDwfa2hjvMX37ev9o1cF2ACzGH1Gq81Qdw0dfmPWuPy2KpvFw50dOdCBTAkEA0XPMVJm+pQm5p3Vkov54COcoOLprKjTLinJg196hZdp66AVpr+S4dKssbSFSlm4XVrx7Kul4mPO8+/PcNhMAkg==";
        //分段解密
        String decerStr = blockDecryptByPrivateKey(content, privateKey);
        return URLDecoder.decode(decerStr); //URL解密
    }

    /**
     * 生成随机密码和加解密测试
     */
    public static void testEncryptDecrypt() {
        //生成公钥和私钥
        Map<Integer, String> keyPair = getKeyPair();
        if (keyPair == null) {
            return;
        }
        //加密字符串
        String password = "100070";
        System.out.println("随机生成的公钥为：" + keyPair.get(0));
        System.out.println("随机生成的私钥为：" + keyPair.get(1));
        String passwordEn = encrypt(password, keyPair.get(0));
        System.out.println(password + "\t加密后的字符串为：" + passwordEn);
        String passwordDe = decrypt(passwordEn, keyPair.get(1));
        System.out.println("还原后的字符串为：" + passwordDe);
    }

    /**
     * 随机生成密钥对：0公钥，1私钥
     */
    public static Map<Integer, String> getKeyPair() {
        try {
            //KeyPairGenerator类用于生成公钥和密钥对，基于RSA算法生成对象
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            //初始化密钥对生成器
            keyPairGen.initialize(KEY_SIZE, new SecureRandom());
            //生成一个密钥对，保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();//得到私钥
            PublicKey publicKey = keyPair.getPublic();//得到公钥
            //得到公钥字符串
            String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
            //得到私钥字符串
            String privateKeyString = new String(Base64.encodeBase64(privateKey.getEncoded()));
            //将公钥和私钥保存到Map
            Map<Integer, String> keyMap = new HashMap<>();
            keyMap.put(0, publicKeyString);//0表示公钥
            keyMap.put(1, privateKeyString);//1表示私钥
            return keyMap;
        } catch (Exception ex) {
            logger.error("获取RSA密钥对失败！ERROR:{}", ex.getMessage());
            return null;
        }
    }

    /**
     * RSA公钥加密，超过117个字节的字符串请使用分段加密blockEncryptByPublicKey。
     * 使用的是1024位的RSA密钥（即128个字节），同时采用PKCS#1填充（至少占用11个字节的填充），那么最大加密数据长度就是117个字节。
     * @param str       加密字符串
     * @param publicKey 公钥
     */
    public static String encrypt(String str, String publicKey) {
        if (StringUtils.isAnyBlank(str, publicKey)) {
            return null;
        }
        try {
            //base64编码的公钥
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
            //RAS加密
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            return Base64.encodeBase64String(cipher.doFinal(str.getBytes(UTF8)));
        } catch (Exception ex) {
            logger.error("RSA公钥加密失败！str:{}，publicKey:{}，ERROR:{}", str, publicKey, ex.getMessage());
            return null;
        }
    }

    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     */
    public static String decrypt(String str, String privateKey) {
        if (StringUtils.isAnyBlank(str, privateKey)) {
            return null;
        }
        try {
            //Base64解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(str.getBytes(UTF8));
            //Base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            PrivateKey priKey = KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
            //RSA解密
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            return new String(cipher.doFinal(inputByte));
        } catch (Exception ex) {
            logger.error("RSA私钥解密失败！str:{}，publicKey:{}，ERROR:{}", str, privateKey, ex.getMessage());
            return null;
        }
    }


    /**
     * 公钥分段加密，用于长度过大的内容，
     *
     * @param str       待加密字符串
     * @param publicKey 公钥
     * @return
     */
    public static String blockEncryptByPublicKey(String str, String publicKey) {
        if (StringUtils.isAnyBlank(str, publicKey)) {
            return str;
        }
        try {
            byte[] dataBytes = str.getBytes(UTF8);
            byte[] keyBytes = Base64.decodeBase64(publicKey.getBytes());
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            Key key = keyFactory.generatePublic(x509EncodedKeySpec);
            // 加密数据，分段加密
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            int inputLength = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offset = 0;
            byte[] cache;
            int i = 0;
            while (inputLength - offset > 0) {
                if (inputLength - offset > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(dataBytes, offset, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataBytes, offset, inputLength - offset);
                }
                out.write(cache, 0, cache.length);
                i++;
                offset = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64.encodeBase64String(encryptedData);
        } catch (Exception ex) {
            logger.error("RSA公钥分段加密失败！str:{}，publicKey:{}，ERROR:{}", str, publicKey, ex.getMessage());
            return null;
        }
    }

    /**
     * 私钥分段解密，用于长度过大的内容
     *
     * @param str        待加密字符串
     * @param privateKey 私钥
     * @return
     */
    public static String blockDecryptByPrivateKey(String str, String privateKey) {
        if (StringUtils.isAnyBlank(str, privateKey)) {
            return str;
        }
        try {
            //Base64解码加密后的字符串
            byte[] dataBytes = Base64.decodeBase64(str.getBytes(UTF8));
            byte[] keyBytes = Base64.decodeBase64(privateKey.getBytes());
            PKCS8EncodedKeySpec pKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
            Key key = keyFactory.generatePrivate(pKCS8EncodedKeySpec);
            // 解密数据，分段解密
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);
            int inputLength = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offset = 0;
            byte[] cache;
            int i = 0;
            while (inputLength - offset > 0) {
                if (inputLength - offset > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(dataBytes, offset, inputLength - offset);
                }
                out.write(cache);
                i++;
                offset = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception ex) {
            logger.error("RSA公钥分段加密失败！str:{}，privateKey:{}，ERROR:{}", str, privateKey, ex.getMessage());
            return null;
        }
    }
}
